package com.base.monitor.module.biz.message.impl;

import cn.hutool.core.util.StrUtil;
import com.base.monitor.framework.exception.CustomerException;
import com.base.monitor.model.constant.CollectConstant;
import com.base.monitor.model.enums.biz.CollectTypeEnum;
import com.base.monitor.model.enums.code.impl.biz.cfg.CfgCode;
import com.base.monitor.model.enums.code.impl.biz.collect.CollectCode;
import com.base.monitor.model.enums.code.impl.common.ResultCode;
import com.base.monitor.model.model.biz.cfg.entity.Indicator;
import com.base.monitor.model.model.biz.collect.entity.CollectData;
import com.base.monitor.module.biz.context.CfgContext;
import com.base.monitor.module.biz.context.ClientContext;
import com.base.monitor.module.biz.handler.HandleCollectDataHandler;
import com.base.monitor.module.biz.message.Message;
import com.base.monitor.module.biz.service.cfg.IndicatorService;
import com.base.monitor.module.biz.util.CRC16Util;
import com.base.monitor.module.biz.util.ChannelUtils;
import com.base.monitor.module.biz.util.ConvertUtils;
import io.netty.channel.ChannelHandlerContext;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;

@Component
@Slf4j
@NoArgsConstructor
public class IndicatorMessage implements Message {

    @Autowired
    private HandleCollectDataHandler handleCollectDataHandler;

    @Autowired
    private IndicatorService indicatorService;

    /**
     * 消息类型
     *
     * @return
     */
    @Override
    public String messageType() {
        return CollectConstant.MESSAGE_TYPE_IS_INDICATOR;
    }

    /**
     * 处理消息
     * @param ctx netty客户端通道信息
     * @param message 消息内容
     */
    @Override
    public void handle(ChannelHandlerContext ctx, String message) {
//        message = "FF0001040A01030A00A930380000000000000C36"; // 电梯测试
        log.info("处理ip为{}的指标消息，消息内容为{}", ctx.channel().remoteAddress(), message);
        // 1.获取ip地址
        String ip = ChannelUtils.formatChannelIp(ctx.channel().remoteAddress().toString());

        // 2.获取信道信息
        ClientContext.ChannelInfo channelInfo = ClientContext.getChannelInfoByIp(ip);
        if (channelInfo == null) {
            log.warn("ip地址为{}的信道信息不存在，忽略本次数据的处理", ip);
            return;
        }

        // 3.解析消息
        CollectData collect = parseCollectDataFromMessage(message);

        // 4.更新指标的在线状态
        CfgContext.addIndicatorOnlineTime(collect.getIndicatorId());

        // 5.推送给远端
        pushCollect(collect);
    }



    /**
     * 将采集数据推送给远端
     * @param collect 推送给远端的数据
     */
    private void pushCollect(CollectData collect) {
        handleCollectDataHandler.addTask(collect);
    }

    /**
     * 解析消息
     *      998877060601030400028760382B
     *          99 标记位，ff
     *          8877 指标id
     *          0606 从第几个字节开始读，读取几个字节
     *          0002876 数据位
     *          382B CRC校验
     * @param message 消息内容
     * @return
     */
    public CollectData parseCollectDataFromMessage(String message) {
        // 1.解析
        Map<String, Object> messageHexMap = doParseMessage(message);

        // 2.校验指标信息是否合法并获取真正的读数
        return validateMessageHexMapAndCalculateValue(messageHexMap);
    }


    /**
     * 校验指标信息是否合法并获取真正的读数
     * @param messageHexMap 指标信息
     * @return
     */
    private CollectData validateMessageHexMapAndCalculateValue(Map<String, Object> messageHexMap) {
        String indicatorId = ((long) messageHexMap.get("indicatorId")) + "";
        if (StrUtil.isBlank(indicatorId)) {
            throw new CustomerException(CollectCode.ILLEGAL_INDICATOR_ID);
        }

        // 根据指标获取对应的指标信息
        Indicator indicator = indicatorService.findOne(indicatorId);
        if (indicator == null) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "没有查询到对应的指标信息");
        }

        // 获取校验位
        String checkHex = (String) messageHexMap.get("checkHex");

        // 获取校验数据
        String checkDataHex = (String) messageHexMap.get("checkDataHex");

        // 获取数据
        String dataStr = (String) messageHexMap.get("dataStr");

        // 校验是否正确
        String auth;
        if (indicator.getAuthCode().toLowerCase().equals("crc")) {
            auth = CRC16Util.crc16(checkDataHex);
        } else if (indicator.getAuthCode().toLowerCase().equals("cs")) {
            auth = CRC16Util.cs(checkDataHex);
        } else {
            auth = "";
        }
        if (!auth.toLowerCase().equals(checkHex.toLowerCase())) {
            throw new CustomerException(CollectCode.VALIDATE_FAIL);
        }

        // 处理分割
        String takeBitStr = indicator.getTakeBit();
        List<String> splitedDataList = splitDataByTakeBit(dataStr, takeBitStr);

        // 处理反转
        List<String> reservedDataList = new ArrayList<>();
        if (indicator.getReverse() == 1) {
            for (String data : splitedDataList) {
                reservedDataList.add(reverseStrByContinuousCount(data));
            }
        } else {
            reservedDataList.addAll(splitedDataList);
        }

        // 处理读取取位和解析取位
        List<DataEntity> scaledDataList = new ArrayList<>();
        List<String> parseScaleList = StrUtil.isBlank(indicator.getParseScale())
                ? new ArrayList<>() : new ArrayList<>(Arrays.asList(indicator.getParseScale().split(",")));
        if (parseScaleList.size() != splitedDataList.size()) {
            throw new CustomerException(CfgCode.TAKE_BIT_LENGTH_IS_NOT_EQUALS_PARSE_SCALE_LENGTH);
        }
        for (int i = 0 ; i < reservedDataList.size() ; i++) {
            scaledDataList.add(handleScale(reservedDataList.get(i), indicator.getReadScale(),
                    Integer.parseInt(parseScaleList.get(i)), indicator.getFloatable(), indicator.getMultiple()));
        }

        // 解析值
        List<String> parsedDataList = new ArrayList<>();
        for (DataEntity dataEntity : scaledDataList) {
            if (dataEntity.getScale() != 10) {
                parsedDataList.add(dataEntity.getData());
                continue;
            }
            String value;
            if (indicator.getFloatable() == 1) {
                double dataNum = hexToFloat(dataEntity.getData());
                BigDecimal bigDecimal = new BigDecimal(dataNum).setScale(2, RoundingMode.HALF_UP);
                value = bigDecimal.doubleValue() + "";
            } else {
                double dataNum = Double.parseDouble(dataEntity.getData()) * Double.parseDouble(indicator.getMultiple());
                value = convertDoubleToString(dataNum, dataEntity.getReturnFloat() ? 1 : 0);
            }
            parsedDataList.add(value);
        }

        // 封装数据并返回
        return new CollectData(indicatorId, CollectTypeEnum.COMMON, parsedDataList);
    }

    /**
     * TODO 现在用枚举实现，今后要做成通用的方法
     * 处理读取进制和解析进制
     * @param raw 待处理的字符串
     * @param readScale 读取进制，暂时只支持十进制10和十六进制16
     * @param parseScale 解析进制，暂时只支持十进制10、二进制2和ascii码0
     * @return
     */
    private DataEntity handleScale(String raw, Integer readScale, Integer parseScale, Integer floatable, String multiple) {
        boolean returnFloat = checkIfReturnFloat(floatable, multiple);
        if (readScale.equals(10) && parseScale.equals(10)) {
            return new DataEntity(raw, 10, returnFloat);
        }
        if (readScale.equals(16) && parseScale.equals(10)) {
            return new DataEntity(ConvertUtils.hexToDec(raw) + "", 10, returnFloat);
        }
        if (readScale.equals(10) && parseScale.equals(2)) {
            return new DataEntity(Integer.toBinaryString(Integer.parseInt(raw)), 2, returnFloat);
        }
        if (readScale.equals(16) && parseScale.equals(2)) {
            return new DataEntity(ConvertUtils.hex2Bin(raw), 2, returnFloat);
        }
        if (readScale.equals(10) && parseScale.equals(0)) {
            return new DataEntity(ConvertUtils.stringToAscii(ConvertUtils.hexToDec(raw) + ""), 0, returnFloat);
        }
        if (readScale.equals(16) && parseScale.equals(0)) {
            return new DataEntity(ConvertUtils.stringToAscii(raw), 0, returnFloat);
        }
        throw new CustomerException(CollectCode.ILLEGAL_SCALE_COLLECT);
    }

    /**
     * 检查要不要返回浮点数
     * @param floatable 指标中是否解析为浮点数
     * @param multiple 指标中倍数
     * @return
     */
    private boolean checkIfReturnFloat(Integer floatable, String multiple) {
        return floatable.equals(1) || Double.parseDouble(multiple) < 1;
    }

    /**
     * 将数据位按照取位进行分割。如取位为2,3，数据位为01 02 03 04 05，那么结果就是["0102", "030405"]
     * @param dataStr 数据位
     * @param takeBitStr 取位
     * @return
     */
    private List<String> splitDataByTakeBit(String dataStr, String takeBitStr) {
        if (StrUtil.isBlank(dataStr)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "处理指标消息时，数据位为空");
        }
        if (StrUtil.isBlank(takeBitStr)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "处理指标消息时，取位为空");
        }
        if (!getDataLengthFromTakeBit(takeBitStr).equals(dataStr.length() / 2)) {
            throw new CustomerException(CollectCode.ILLEGAL_DATA_LENGTH);
        }
        String[] split = takeBitStr.split(",");
        List<String> result = new ArrayList<>();
        int alreadyBit = 0;
        for (String count : split) {
            String data = dataStr.substring(alreadyBit * 2, (alreadyBit + Integer.parseInt(count)) * 2);
            alreadyBit += Integer.parseInt(count);
            result.add(data);
        }
        return result;
    }

    /**
     * 获取takeBitStr指定的数据位的总长度
     * @param takeBitStr 待获取长度的字符串
     * @return
     */
    private Integer getDataLengthFromTakeBit(String takeBitStr) {
        if (StrUtil.isBlank(takeBitStr)) {
            return 0;
        }
        String[] split = takeBitStr.split(",");
        Integer count = 0;
        for (String num : split) {
            count += Integer.parseInt(num);
        }
        return count;
    }

    private String convertDoubleToString(double val, int length) {
        BigDecimal b = new BigDecimal(val);
        return b.setScale(length, BigDecimal.ROUND_HALF_UP) + "";
    }

    private String convertDoubleToString(double val) {
        return convertDoubleToString(val, 0);
    }

    /**
     * 解析指标
     * @param message 指标信息
     * @return
     */
    private Map<String, Object> doParseMessage(String message) {
        // 1.解析指标id
        // 1.1.获取指标
        String indicatorHex = message.substring(2, 6);
        long indicatorId = ConvertUtils.hexToDec(indicatorHex);

        // 2.解析开始位
        String startBitHex = message.substring(6, 8);
        int startBit = ConvertUtils.hexToDec(startBitHex);

        // 3.解析取位
        String takeBitHex = message.substring(8, 10);
        int takeBit = ConvertUtils.hexToDec(takeBitHex);

        // 4.解析数据位
        String dataStr = message.substring((5 + startBit - 1) * 2, (5 + startBit + takeBit - 1) * 2);

        // 5.解析校验数据
        String checkHex = message.substring((5 + startBit + takeBit - 1) * 2);

        // 6.解析需要校验的数据
        String checkDataHex = message.substring(10, (5 + startBit + takeBit - 1) * 2);

        // 6.封装数据并返回
        Map<String, Object> map = new HashMap<>();
        map.put("indicatorId", indicatorId);
        map.put("dataStr", dataStr);
        map.put("checkHex", checkHex);
        map.put("checkDataHex", checkDataHex);
        return map;
    }

    /**
     * 将4字节的16进制字符串，转换为32位带符号的十进制浮点型
     * @param str 4字节 16进制字符
     * @return
     */
    public static float hexToFloat(String str) {
        return Float.intBitsToFloat(new BigInteger(str, 16).intValue());
    }

    /**
     * 将带符号的32位浮点数装换为16进制
     * @param value
     * @return
     */
    public static String floatToHexString(Float value) {
        return Integer.toHexString(Float.floatToIntBits(value));
    }

    /**
     * 两个两个字符反转
     * @param rawStr 待转换的字符串
     * @return
     */
    public static String reverseStrByContinuousCount(String rawStr) {
        StringBuilder sb = new StringBuilder();
        for (int i = rawStr.length() / 2; i > 0; i--) {
            sb.append(rawStr, i * 2 - 2, i * 2);
        }
        return sb.toString();
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class DataEntity {
        private String data; // 当前数据
        private Integer scale; // 当前数据是几进制的
        private Boolean returnFloat; // 当前数据是否要返回浮点数。只有两种情况要返回，multiple小于1，或者floatable为1
    }
}
